<!DOCTYPE html><html lang="zh-Hans"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="Android-Cling投屏"><meta name="keywords" content="投屏"><meta name="author" content="WuShaohong"><meta name="copyright" content="WuShaohong"><title>Android-Cling投屏 | WuShaohongのBlog</title><link rel="shortcut icon" href="/wushaohong/melody-favicon.ico"><link rel="stylesheet" href="/wushaohong/css/index.css?version=1.8.2"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.8.2"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/wushaohong/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  hexoVersion: '5.2.0'
} </script><meta name="generator" content="Hexo 5.2.0"><link rel="alternate" href="/wushaohong/atom.xml" title="WuShaohongのBlog" type="application/atom+xml">
</head><body><canvas class="fireworks"></canvas><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Android-%E5%9F%BA%E4%BA%8ECling%E6%8A%95%E5%B1%8F%E5%AE%9E%E7%8E%B0"><span class="toc-number">1.</span> <span class="toc-text">Android - 基于Cling投屏实现</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8A%95%E5%B1%8F%E6%8A%80%E6%9C%AF"><span class="toc-number">1.1.</span> <span class="toc-text">投屏技术</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DLNA"><span class="toc-number">1.2.</span> <span class="toc-text">DLNA</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#UPNP%E5%8D%8F%E8%AE%AE"><span class="toc-number">1.2.1.</span> <span class="toc-text">UPNP协议</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Miracast"><span class="toc-number">1.3.</span> <span class="toc-text">Miracast</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AirPlay"><span class="toc-number">1.4.</span> <span class="toc-text">AirPlay</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Cling"><span class="toc-number">1.5.</span> <span class="toc-text">Cling</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%A3%E7%A0%81"><span class="toc-number">1.6.</span> <span class="toc-text">代码</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1%E3%80%81%E5%90%AF%E5%8A%A8Upnp%E6%9C%8D%E5%8A%A1%EF%BC%8C%E6%90%9C%E7%B4%A2%E6%8A%95%E5%B1%8F%E8%AE%BE%E5%A4%87"><span class="toc-number">1.6.1.</span> <span class="toc-text">1、启动Upnp服务，搜索投屏设备</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2%E3%80%81%E5%8D%8F%E8%AE%AE%E5%B0%81%E8%A3%85"><span class="toc-number">1.6.2.</span> <span class="toc-text">2、协议封装</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3%E3%80%81%E6%8A%95%E5%B1%8F%E8%A7%86%E9%A2%91%E3%80%81%E9%9F%B3%E9%A2%91%E3%80%81%E5%9B%BE%E7%89%87"><span class="toc-number">1.6.3.</span> <span class="toc-text">3、投屏视频、音频、图片</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4%E3%80%81%E8%8E%B7%E5%8F%96%E5%AA%92%E4%BD%93%E4%BF%A1%E6%81%AF"><span class="toc-number">1.6.4.</span> <span class="toc-text">4、获取媒体信息</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5%E3%80%81%E6%92%AD%E6%94%BE%E6%8E%A7%E5%88%B6%EF%BC%8C%E6%9A%82%E5%81%9C%E6%92%AD%E6%94%BE-%E6%81%A2%E5%A4%8D%E6%92%AD%E6%94%BE-%E5%81%9C%E6%AD%A2%E6%92%AD%E6%94%BE"><span class="toc-number">1.6.5.</span> <span class="toc-text">5、播放控制，暂停播放 &#x2F; 恢复播放 &#x2F; 停止播放</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6%E3%80%81%E8%BF%9B%E5%BA%A6%E6%8E%A7%E5%88%B6%EF%BC%8C%E9%9F%B3%E9%87%8F%E6%8E%A7%E5%88%B6"><span class="toc-number">1.6.6.</span> <span class="toc-text">6、进度控制，音量控制</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9C%AA%E5%AE%8C%E5%BE%85%E7%BB%AD"><span class="toc-number">1.7.</span> <span class="toc-text">未完待续</span></a></li></ol></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/wushaohong/img/avatar.png"></div><div class="author-info__name text-center">WuShaohong</div><div class="author-info__description text-center"></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/wushaohong/archives"><span class="pull-left">文章</span><span class="pull-right">7</span></a><a class="author-info-articles__tags article-meta" href="/wushaohong/tags"><span class="pull-left">标签</span><span class="pull-right">5</span></a><a class="author-info-articles__categories article-meta" href="/wushaohong/categories"><span class="pull-left">分类</span><span class="pull-right">2</span></a></div></div></div><div id="content-outer"><div id="top-container" style="background-image: url(https://timgsa.baidu.com/timg?image&amp;quality=80&amp;size=b9999_10000&amp;sec=1602489080558&amp;di=0dbbed72cc7f877b951b367d4a9ed292&amp;imgtype=0&amp;src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201504%2F05%2F094406wb74eokgpu7fuxnz.jpg)"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/wushaohong/">WuShaohongのBlog</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/hexo-blog/">搜索</a><a class="site-page" href="/hexo-blog/">首页</a><a class="site-page" href="/hexo-blog/categories/">分类</a><a class="site-page" href="/hexo-blog/tags/">标签</a><a class="site-page" href="/hexo-blog/archives/">时间轴</a><a class="site-page" href="/hexo-blog/about/">关于</a></span><span class="pull-right"></span></div><div id="post-info"><div id="post-title">Android-Cling投屏</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2020-11-11</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/wushaohong/categories/Android/">Android</a><div class="post-meta-wordcount"><span>字数总计: </span><span class="word-count">4k</span><span class="post-meta__separator">|</span><span>阅读时长: 17 分钟</span></div></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><p>​    一年多之前买了个小米盒子，放在出租屋，HDMI 外接显示器当电视使用，用着确实很nice，看网络电视直播，用手机或者ipad投屏看剧。之前也业余搞过一个观看m3u8 源电视直播的app，主要是一些CCTV，卫视频道，一些电影和动漫直播链接都是虎牙拿过来（不稳定，老是失效），app 运行在小米盒子上还算不错，也可以运行在基于Android 的电视平台。</p>
<p>​    小米盒子的投屏服务端用的是乐播投屏，平时使用投屏的时候也有乐播的广告logo。看了一下乐播的投屏，开发的话直接用sdk 固然是最快最便捷的，不过乐播的sdk 是要钱的。我对钱没兴趣，所以我选择撸起袖子自己琢磨。查阅了一下手机投屏的技术方案，有许所获。</p>
<h2 id="Android-基于Cling投屏实现"><a href="#Android-基于Cling投屏实现" class="headerlink" title="Android - 基于Cling投屏实现"></a>Android - 基于Cling投屏实现</h2><h3 id="投屏技术"><a href="#投屏技术" class="headerlink" title="投屏技术"></a>投屏技术</h3><p>1、推送投屏</p>
<p>​            推送投屏的方式，更多类似于“Wifi点播”，由推送端推送播放链接，接收端接收链接（实际当中，链接等信息是需要封装在具体的协议当中发送），进行播放。接收端播放过程中较为自主，只依赖播放源，网速等因素，无需依赖推送端。也就是说通过手机投屏app，将在线电影投屏到TV端播放，投屏成功之后，就可以结束app，不影响TV端的播放。</p>
<p>2、镜像同步</p>
<p>​            镜像同步的方式是”画面即时同步传输“，影像数据是实时发出、接收并同时解码播放，不同于在线点播时的“先缓存一部分、再边下载边播放”。所以，一般镜像同屏时，投影出来的画面对比手机画面，都会有一定的延迟。具体的延迟程度取决于手机的硬件品质（如CPU、运存等，毕竟手机是作为信号发射源的，本身的硬件决定了信号发出的品质）；外在影响因素还有：如果是插线同屏的，数据通过手机线进行传输，如果数据线老化、或者品质不好，传输的质量自然也会受影响；而“无线同屏”的话，虽然没有线的束缚，但是“无线信号的传输”同样有外界的影响因素，比如路由器的品质、其他设备的干扰等。因此，无论是“插线同屏”或者“无线同屏”，因为“镜像同步”这种要求性较高的技术特点，实际使用中多少都会有些延迟，但一般2秒以内的延迟属于正常范围，当然目前技术可以实现更低的延时。</p>
<p>​            个人认为，镜像同步和分屏是有区别的。日常开会，主讲人通常将会议内容，通过“投屏/投影”，投放到大屏幕上面。这里的我们所说的投屏，通常是镜像同步，或者分屏。</p>
<p>​            分屏，也就是通过显卡硬件输出方式实现多路信号输出，由硬件的IO设备直接输出显示（就像我们多屏办公的样子，一台主机外接多个显示器），不需要软件编码与解码，优点延时低。</p>
<p>​            投影，和分屏同理，只是最终的显示方式是投影仪投影显示。</p>
<h3 id="DLNA"><a href="#DLNA" class="headerlink" title="DLNA"></a>DLNA</h3><p>​            DLNA（DIGITAL LIVING NETWORK ALLIANCE，数字生活网络联盟) 其前身是DHWG （Digital Home Working Group，数字家庭工作组），成立于2003年6月24 日， 是由索尼、英特尔、微软等发起成立的一个非营利性的、合作性质的商业组织。（百度….）</p>
<h4 id="UPNP协议"><a href="#UPNP协议" class="headerlink" title="UPNP协议"></a>UPNP协议</h4><p>​            通用即插即用（英语：Universal Plug and Play，简称UPnP）是由“通用即插即用论坛”（UPnP™ Forum）推广的一套网络协议。该协议的目标是使家庭网络（数据共享、通信和娱乐）和公司网络中的各种设备能够相互无缝连接，并简化相关网络的实现。UPnP通过定义和发布基于开放、因特网通讯网协议标准的UPnP设备控制协议来实现这一目标。<br>UPnP这个概念是从即插即用（Plug-and-play）派生而来的，即插即用是一种热拔插技术。</p>
<p><img src="/wushaohong/2020/11/11/Android-Cling%E6%8A%95%E5%B1%8F/1.jpg" alt="UPNP协议"></p>
<h3 id="Miracast"><a href="#Miracast" class="headerlink" title="Miracast"></a>Miracast</h3><p>​            Miracast是由Wi-Fi联盟于2012年所制定，以Wi-Fi直连为基础的无线显示标准。支持此标准的设备可通过无线方式分享视频画面，例如手机可通过Miracast将影片或照片直接在电视或其他装置播放而无需受到连接线缆长度的影响。与DLNA不同的是，Miracast 也有类似于AirPlay的镜像功能，可以将手机中屏幕内容直接投放到高清电视屏幕里，这样你也可以通过电视屏幕来玩游戏了。Android4.2版本以后系统标配此功能（在设置或显示菜单中可以找到，应用名称诸如：Wlan display、Wifi display、Miracast、Allshare cast、Mirroring screen、无线显示等，只是各手机厂家命名不同而已）。可以将手机屏幕通过无线显示接收器将画面无线传输到其他较大屏幕上，如客厅电视，会议室投影仪。画面传输延时150ms以下，几乎同时。点击此功能，但没有任何反应，因为此项功能需要配件支持。<br>​            个人使用感觉，Miracast的镜像功能真的很卡…</p>
<h3 id="AirPlay"><a href="#AirPlay" class="headerlink" title="AirPlay"></a>AirPlay</h3><p>​            AirPlay 是苹果开发的一种无线技术，可以通过WiFi 将iPhone 、iPad、iPodtouch 等iOS 设备上的包括图片、音频、视频通过无线的方式传输到支持AirPlay 设备。AirPlay 具备与DLNA 所没有的镜像功能，这一功能叫AirPlay 镜像，可将iPhone 或iPad 上的画面无线传输到电视上，也就是说你设备显示的是什么，电视屏幕显示就就是什么，而不仅限于图片和视频。你可以拿着iPad 来当做方向盘，然后看着大屏玩游戏。另外AirPlay 镜像最牛地方是它可以实现双屏游戏，让你的游戏有更多的交互。比如，电视里显示的是游戏画面，而iPad 上显示的是比赛的路线图。<br>​            目前，苹果的AirPlay 更多的只适用于认证过的苹果设备，目前支持这一技术的主要是苹果自己的设备包括了iPad、iPhone、Apple TV等</p>
<h3 id="Cling"><a href="#Cling" class="headerlink" title="Cling"></a>Cling</h3><p>常用的投屏开源库有：Platinum、CyberGarage、Cling</p>
<p>​        Cling是一个Java开源项目，开发者可直接编译源码生成jar包导入到Android项目中。目前Cling已停止维护。</p>
<p>​        Platinum是一个C库，它支持编译成多个平台的库，如Windows、Mac、IOS和Android等。但其编译流程相对来说比较复杂，Android使用Platinum开发需要用到jni。</p>
<p>​        CyberGarage是一个Java Upnp开发包，开发者将其项目源码添加到Android工程当中，作为Android Library或者 Java Library直接使用。CyberGarage提供了jar包下载地址，但CyberGarage源码存在一些bug，需要对源码进行修改，因此不建议直接下载jar包。</p>
<p>Cling是实现Dlna的Upnp协议的第三方投屏开源库。</p>
<p><a target="_blank" rel="noopener" href="https://hexo.io/docs/server.html">Cling开源库GitHub地址</a></p>
<p><img src="/wushaohong/2020/11/11/Android-Cling%E6%8A%95%E5%B1%8F/2.jpg" alt="实现Demo效果"></p>
<p>Android 项目导入依赖</p>
<p>项目 build.gradle</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">buildscript</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="string">repositories</span> &#123;</span><br><span class="line">        <span class="string">...</span></span><br><span class="line"></span><br><span class="line">        <span class="string">mavenCentral()</span></span><br><span class="line">        <span class="string">maven</span> &#123;</span><br><span class="line">            <span class="string">url</span> <span class="string">&#x27;http://4thline.org/m2&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="string">...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="string">allprojects</span> &#123;</span><br><span class="line">    <span class="string">repositories</span> &#123;</span><br><span class="line">        <span class="string">...</span></span><br><span class="line"></span><br><span class="line">        <span class="string">mavenCentral()</span></span><br><span class="line">        <span class="string">maven</span> &#123;</span><br><span class="line">            <span class="string">url</span> <span class="string">&#x27;http://4thline.org/m2&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>app build.gradle</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">android</span> &#123;</span><br><span class="line">    <span class="string">...</span></span><br><span class="line"></span><br><span class="line">    <span class="string">packagingOptions</span> &#123;</span><br><span class="line">        <span class="string">exclude</span> <span class="string">&#x27;META-INF/LICENSE.txt&#x27;</span></span><br><span class="line">        <span class="string">exclude</span> <span class="string">&#x27;META-INF/beans.xml&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="string">dependencies</span> &#123;</span><br><span class="line">    <span class="string">...</span></span><br><span class="line"></span><br><span class="line">    <span class="string">//</span> <span class="string">Cling</span> <span class="string">library</span></span><br><span class="line">    <span class="string">implementation</span> <span class="string">&#x27;org.fourthline.cling:cling-core:2.1.1&#x27;</span></span><br><span class="line">    <span class="string">implementation</span> <span class="string">&#x27;org.fourthline.cling:cling-support:2.1.1&#x27;</span></span><br><span class="line">    <span class="string">//</span> <span class="string">Jetty</span> <span class="string">library</span></span><br><span class="line">    <span class="string">implementation</span> <span class="string">&#x27;org.eclipse.jetty:jetty-server:8.1.12.v20130726&#x27;</span></span><br><span class="line">    <span class="string">implementation</span> <span class="string">&#x27;org.eclipse.jetty:jetty-servlet:8.1.12.v20130726&#x27;</span></span><br><span class="line">    <span class="string">implementation</span> <span class="string">&#x27;org.eclipse.jetty:jetty-client:8.1.12.v20130726&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>AndroidManifest.xml</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 权限 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.INTERNET&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.ACCESS_WIFI_STATE&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.CHANGE_WIFI_MULTICAST_STATE&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.ACCESS_NETWORK_STATE&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.WAKE_LOCK&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.READ_EXTERNAL_STORAGE&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;</span> /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 声明upnp服务实现类（服务） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">service</span> <span class="attr">android:name</span>=<span class="string">&quot;org.fourthline.cling.android.AndroidUpnpServiceImpl&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure>



<h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><h4 id="1、启动Upnp服务，搜索投屏设备"><a href="#1、启动Upnp服务，搜索投屏设备" class="headerlink" title="1、启动Upnp服务，搜索投屏设备"></a>1、启动Upnp服务，搜索投屏设备</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 声明</span></span><br><span class="line"><span class="comment">// Upnp服务</span></span><br><span class="line"><span class="keyword">private</span> AndroidUpnpService mUpnpService = <span class="keyword">null</span>;</span><br><span class="line"><span class="comment">// Upnp控制点</span></span><br><span class="line"><span class="keyword">private</span> ControlPoint mControlPoint;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 服务连接</span></span><br><span class="line"><span class="keyword">private</span> ServiceConnection serviceConnection;</span><br><span class="line"></span><br><span class="line">serviceConnection = <span class="keyword">new</span> ServiceConnection() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceConnected</span><span class="params">(ComponentName componentName, IBinder iBinder)</span> </span>&#123;</span><br><span class="line">        mUpnpService = (AndroidUpnpService) iBinder;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 服务回调</span></span><br><span class="line">        mUpnpService.getRegistry().addListener(<span class="keyword">new</span> DefaultRegistryListener() &#123;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deviceAdded</span><span class="params">(Registry registry, <span class="keyword">final</span> Device device)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">super</span>.deviceAdded(registry, device);</span><br><span class="line"></span><br><span class="line">                Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;找到设备：&quot;</span> + device.getDisplayString());</span><br><span class="line">                <span class="comment">// 加入到发现的设备列表</span></span><br><span class="line">                runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        deviceAdapter.addDevice(device);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deviceRemoved</span><span class="params">(Registry registry, <span class="keyword">final</span> Device device)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">super</span>.deviceRemoved(registry, device);</span><br><span class="line"></span><br><span class="line">                Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;移除设备：&quot;</span> + device.getDisplayString());</span><br><span class="line">                <span class="comment">// 从发现的设备列表中移除</span></span><br><span class="line">                runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        deviceAdapter.removeDevice(device);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        mControlPoint = mUpnpService.getControlPoint();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动 Upnp服务</span></span><br><span class="line">    Intent intent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, AndroidUpnpServiceImpl.class);</span><br><span class="line">    bindService(intent, serviceConnection, BIND_AUTO_CREATE);</span><br></pre></td></tr></table></figure>



<p>启动AndroidUpnpServiceImpl 服务后，服务自动开始搜索投屏设备，你也可以通过以下代码手动搜索</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mControlPoint.search();</span><br></pre></td></tr></table></figure>



<h4 id="2、协议封装"><a href="#2、协议封装" class="headerlink" title="2、协议封装"></a>2、协议封装</h4><p>在步骤1当中，我们已经搜索到设备了</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 协议头</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DIDL_LITE_FOOTER = <span class="string">&quot;&lt;/DIDL-Lite&gt;&quot;</span>;</span><br><span class="line"><span class="comment">// 协议尾</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DIDL_LITE_HEADER = <span class="string">&quot;&lt;?xml version=\&quot;1.0\&quot;?&gt;&quot;</span> +</span><br><span class="line">    <span class="string">&quot;&lt;DIDL-Lite &quot;</span> + <span class="string">&quot;xmlns=\&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\&quot; &quot;</span> +</span><br><span class="line">    <span class="string">&quot;xmlns:dc=\&quot;http://purl.org/dc/elements/1.1/\&quot; &quot;</span> + <span class="string">&quot;xmlns:upnp=\&quot;urn:schemas-upnp-org:metadata-1-0/upnp/\&quot; &quot;</span> +</span><br><span class="line">    <span class="string">&quot;xmlns:dlna=\&quot;urn:schemas-dlna-org:metadata-1-0/\&quot;&gt;&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类型定义</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> VIDEO_TYPE = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> AUDIO_TYPE = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> IMAGE_TYPE = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 投屏前需要确保 mUpnpService != null || mCurrentDevice != null || mControlPoint != null</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 媒体投屏（视频，音频，图片）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setAVTransportURI</span><span class="params">(String url, <span class="keyword">int</span> mediaType)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// mCurrentDevice 为在步骤1 搜索到的设备中的一个</span></span><br><span class="line">    Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line"></span><br><span class="line">    String metadata = pushMediaToRender(url, <span class="string">&quot;id&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;0&quot;</span>, mediaType);</span><br><span class="line">    mControlPoint.execute(<span class="keyword">new</span> SetAVTransportURI(service, url, metadata) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">super</span>.success(invocation);</span><br><span class="line"></span><br><span class="line">            runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&quot;投屏成功&quot;</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">            Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;投屏失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建媒体协议信息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">pushMediaToRender</span><span class="params">(String url, String id, String name, String duration, <span class="keyword">int</span> mediaType)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">long</span> size = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">long</span> bitrate = <span class="number">0</span>;</span><br><span class="line">    Res res = <span class="keyword">new</span> Res(<span class="keyword">new</span> MimeType(ProtocolInfo.WILDCARD, ProtocolInfo.WILDCARD), size, url);</span><br><span class="line"></span><br><span class="line">    String creator = <span class="string">&quot;unknow&quot;</span>;</span><br><span class="line">    String resolution = <span class="string">&quot;unknow&quot;</span>;</span><br><span class="line"></span><br><span class="line">    String metadata = <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (VIDEO_TYPE == mediaType) &#123;</span><br><span class="line">        VideoItem videoItem = <span class="keyword">new</span> VideoItem(id, <span class="string">&quot;0&quot;</span>, name, creator, res);</span><br><span class="line">        metadata = createItemMetadata(videoItem);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (AUDIO_TYPE == mediaType) &#123;</span><br><span class="line">        AudioItem audioItem = <span class="keyword">new</span> AudioItem(id, <span class="string">&quot;0&quot;</span>, name, creator, res);</span><br><span class="line">        metadata = createItemMetadata(audioItem);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (IMAGE_TYPE == mediaType) &#123;</span><br><span class="line">        ImageItem imageItem = <span class="keyword">new</span> ImageItem(id, <span class="string">&quot;0&quot;</span>, name, creator, res);</span><br><span class="line">        metadata = createItemMetadata(imageItem);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;metadata: &quot;</span> + metadata);</span><br><span class="line">    <span class="keyword">return</span> metadata;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 协议封装</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">createItemMetadata</span><span class="params">(DIDLObject item)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    StringBuilder metadata = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">    metadata.append(DIDL_LITE_HEADER);</span><br><span class="line"></span><br><span class="line">    metadata.append(String.format(<span class="string">&quot;&lt;item id=\&quot;%s\&quot; parentID=\&quot;%s\&quot; restricted=\&quot;%s\&quot;&gt;&quot;</span>, item.getId(), item.getParentID(), item.isRestricted() ? <span class="string">&quot;1&quot;</span> : <span class="string">&quot;0&quot;</span>));</span><br><span class="line"></span><br><span class="line">    metadata.append(String.format(<span class="string">&quot;&lt;dc:title&gt;%s&lt;/dc:title&gt;&quot;</span>, item.getTitle()));</span><br><span class="line">    String creator = item.getCreator();</span><br><span class="line">    <span class="keyword">if</span> (creator != <span class="keyword">null</span>) &#123;</span><br><span class="line">        creator = creator.replaceAll(<span class="string">&quot;&lt;&quot;</span>, <span class="string">&quot;_&quot;</span>);</span><br><span class="line">        creator = creator.replaceAll(<span class="string">&quot;&gt;&quot;</span>, <span class="string">&quot;_&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    metadata.append(String.format(<span class="string">&quot;&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;&quot;</span>, creator));</span><br><span class="line"></span><br><span class="line">    metadata.append(String.format(<span class="string">&quot;&lt;upnp:class&gt;%s&lt;/upnp:class&gt;&quot;</span>, item.getClazz().getValue()));</span><br><span class="line"></span><br><span class="line">    SimpleDateFormat sdf = <span class="keyword">new</span> SimpleDateFormat(<span class="string">&quot;yyyy-MM-dd&#x27;T&#x27;HH:mm:ss&quot;</span>);</span><br><span class="line">    Date now = <span class="keyword">new</span> Date();</span><br><span class="line">    String time = sdf.format(now);</span><br><span class="line">    metadata.append(String.format(<span class="string">&quot;&lt;dc:date&gt;%s&lt;/dc:date&gt;&quot;</span>, time));</span><br><span class="line"></span><br><span class="line">    Res res = item.getFirstResource();</span><br><span class="line">    <span class="keyword">if</span> (res != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// protocol info</span></span><br><span class="line">        String protocolInfo = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        ProtocolInfo pi = res.getProtocolInfo();</span><br><span class="line">        <span class="keyword">if</span> (pi != <span class="keyword">null</span>) &#123;</span><br><span class="line">            protocolInfo = String.format(<span class="string">&quot;protocolInfo=\&quot;%s:%s:%s:%s\&quot;&quot;</span>, pi.getProtocol(), pi.getNetwork(), pi.getContentFormatMimeType(), pi</span><br><span class="line">                                         .getAdditionalInfo());</span><br><span class="line">        &#125;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;protocolInfo: &quot;</span> + protocolInfo);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// resolution, extra info, not adding yet</span></span><br><span class="line">        String resolution = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (res.getResolution() != <span class="keyword">null</span> &amp;&amp; res.getResolution().length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            resolution = String.format(<span class="string">&quot;resolution=\&quot;%s\&quot;&quot;</span>, res.getResolution());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// duration</span></span><br><span class="line">        String duration = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (res.getDuration() != <span class="keyword">null</span> &amp;&amp; res.getDuration().length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            duration = String.format(<span class="string">&quot;duration=\&quot;%s\&quot;&quot;</span>, res.getDuration());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// res begin</span></span><br><span class="line">        <span class="comment">// metadata.append(String.format(&quot;&lt;res %s&gt;&quot;, protocolInfo)); // no resolution &amp; duration yet</span></span><br><span class="line">        metadata.append(String.format(<span class="string">&quot;&lt;res %s %s %s&gt;&quot;</span>, protocolInfo, resolution, duration));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// url</span></span><br><span class="line">        String url = res.getValue();</span><br><span class="line">        metadata.append(url);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// res end</span></span><br><span class="line">        metadata.append(<span class="string">&quot;&lt;/res&gt;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    metadata.append(<span class="string">&quot;&lt;/item&gt;&quot;</span>);</span><br><span class="line"></span><br><span class="line">    metadata.append(DIDL_LITE_FOOTER);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> metadata.toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="3、投屏视频、音频、图片"><a href="#3、投屏视频、音频、图片" class="headerlink" title="3、投屏视频、音频、图片"></a>3、投屏视频、音频、图片</h4><p> 投屏前需要确保 mUpnpService != null || mCurrentDevice != null || mControlPoint != null</p>
<p>*地址可能已经失效</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 投屏视频</span></span><br><span class="line">String url = <span class="string">&quot;http://vfx.mtime.cn/Video/2019/03/14/mp4/190314223540373995.mp4&quot;</span>;</span><br><span class="line">setAVTransportURI(url, VIDEO_TYPE);</span><br><span class="line"><span class="comment">// 投屏音频</span></span><br><span class="line">String mp3 = <span class="string">&quot;http://111.230.29.242:8080/cheshi.mp3&quot;</span>;</span><br><span class="line">setAVTransportURI(mp3, AUDIO_TYPE);</span><br><span class="line"><span class="comment">// 投屏图片</span></span><br><span class="line">String image = <span class="string">&quot;https://timgsa.baidu.com/timg?image&amp;quality=80&amp;size=b9999_10000&amp;sec=1597923118159&amp;di=5d0add2fc13243068afbf07e2ce2842a&amp;imgtype=0&amp;src=http%3A%2F%2Fwww.anqu.com%2Fuploads%2Fallimg%2F1208%2F39-120R3093301.jpg&quot;</span>;</span><br><span class="line">setAVTransportURI(image, IMAGE_TYPE);</span><br></pre></td></tr></table></figure>



<h4 id="4、获取媒体信息"><a href="#4、获取媒体信息" class="headerlink" title="4、获取媒体信息"></a>4、获取媒体信息</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> GetMediaInfo(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取媒体信息成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">received</span><span class="params">(ActionInvocation invocation, <span class="keyword">final</span> MediaInfo mediaInfo)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 媒体信息封装在 mediaInfo 对象当中</span></span><br><span class="line">        <span class="comment">// 包括总时长，播放地址等等</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取媒体信息失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h4 id="5、播放控制，暂停播放-恢复播放-停止播放"><a href="#5、播放控制，暂停播放-恢复播放-停止播放" class="headerlink" title="5、播放控制，暂停播放 / 恢复播放 / 停止播放"></a>5、播放控制，暂停播放 / 恢复播放 / 停止播放</h4><p>暂停播放</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> Pause(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line"></span><br><span class="line">        runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&quot;暂停播放&quot;</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;暂停失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>恢复播放</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> Play(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line"></span><br><span class="line">        runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&quot;恢复播放&quot;</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;恢复失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>停止播放</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> Stop(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line"></span><br><span class="line">        runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&quot;停止播放&quot;</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;停止失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h4 id="6、进度控制，音量控制"><a href="#6、进度控制，音量控制" class="headerlink" title="6、进度控制，音量控制"></a>6、进度控制，音量控制</h4><p>获取进度</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> GetPositionInfo(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取进度成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">received</span><span class="params">(ActionInvocation invocation, PositionInfo positionInfo)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, positionInfo.toString());</span><br><span class="line">        <span class="comment">// 进度 positionInfo.getTrackDuration()</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取进度失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>Seek进度</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;AVTransport&quot;</span>));</span><br><span class="line"><span class="comment">// progress 为 &quot;00:00:00&quot;，String 类型</span></span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> Seek(service, progress) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>获取音量</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;RenderingControl&quot;</span>));</span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> GetVolume(service) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取音量成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">received</span><span class="params">(ActionInvocation actionInvocation, <span class="keyword">int</span> currentVolume)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;当前音量：&quot;</span> + currentVolume);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;获取音量失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>设置音量</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Service service = mCurrentDevice.findService(<span class="keyword">new</span> UDAServiceType(<span class="string">&quot;RenderingControl&quot;</span>));</span><br><span class="line"><span class="comment">// volume 0 - 100 （小米盒子，其他设备没有测试具体范围）</span></span><br><span class="line">mControlPoint.execute(<span class="keyword">new</span> SetVolume(service, volume) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">success</span><span class="params">(ActionInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.success(invocation);</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;设置音量成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failure</span><span class="params">(ActionInvocation invocation, UpnpResponse operation, String defaultMsg)</span> </span>&#123;</span><br><span class="line">        Log.e(<span class="string">&quot;wush&quot;</span>, <span class="string">&quot;设置音量失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h3 id="未完待续"><a href="#未完待续" class="headerlink" title="未完待续"></a>未完待续</h3><p>​            以上是推送模式实现的网络媒体资源播放，主要通过Cling实现的upnp协议，将媒体链接等播放信息封装成协议数据，发送给播放端进行播放。</p>
<p>​            还有一种是投屏播放手机本地视频（音频，图片），播放本地资源相对复杂一点，原理是需要在手机本地开启媒体服务推流，生成本地媒体资源的局域网访问地址，再推送给播放端播放（当然也是需要同一个局域网）。</p>
<p>​            有空持续更新…</p>
<p><img src="/wushaohong/2020/11/11/Android-Cling%E6%8A%95%E5%B1%8F/3.jpg"></p>
</div></article><div class="post-meta__tag-list"><a class="post-meta__tags" href="/wushaohong/tags/%E6%8A%95%E5%B1%8F/">投屏</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/wushaohong/2020/11/11/Android%E4%B8%80%E8%A1%8C%E6%B3%A8%E8%A7%A3%E7%94%B3%E8%AF%B7%E6%9D%83%E9%99%90AOP%E6%96%B9%E5%BC%8F%E6%9C%80%E4%BD%B3%E5%AE%9E%E7%8E%B0/"><i class="fa fa-chevron-left">  </i><span>Android (Java)一行注解申请权限AOP方式最佳实现</span></a></div><div class="next-post pull-right"><a href="/wushaohong/2020/10/13/Hexo&amp;Gitee/"><span>Hexo 搭建博客与部署Gitee</span><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer class="footer-bg" style="background-image: url(https://timgsa.baidu.com/timg?image&amp;quality=80&amp;size=b9999_10000&amp;sec=1602489080558&amp;di=0dbbed72cc7f877b951b367d4a9ed292&amp;imgtype=0&amp;src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201504%2F05%2F094406wb74eokgpu7fuxnz.jpg)"><div class="layout" id="footer"><div class="copyright">&copy;2020 - 2021 By WuShaohong</div><div class="framework-info"><span>驱动 - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>主题 - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-eye"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/wushaohong/js/utils.js?version=1.8.2"></script><script src="/wushaohong/js/fancybox.js?version=1.8.2"></script><script src="/wushaohong/js/sidebar.js?version=1.8.2"></script><script src="/wushaohong/js/copy.js?version=1.8.2"></script><script src="/wushaohong/js/fireworks.js?version=1.8.2"></script><script src="/wushaohong/js/transition.js?version=1.8.2"></script><script src="/wushaohong/js/scroll.js?version=1.8.2"></script><script src="/wushaohong/js/head.js?version=1.8.2"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script></body></html>